//
//  HHSerialTaskManager.swift
//  testTaskQueue
//
//  Created by kingdee on 2021/2/4.
//

import UIKit

/*
 串行任务管理：线程安全类；

 1. 是否保证成功: 保证成功则会进入重试策略进行重试，超过重试次数则会抛出；不保证成功则执行完一次就认为此任务结束；
 2. 任务是否严格串行: 非严格串行，上个任务执行完成后，会执行下个任务，上个任务若保证成功且未成功情况下完成则会进入重试策略中等待再次触发, 重试的时间策略并不精准、受定时器准确率影响；
                   开启严格串行上个任务要求一定要成功且成功后才会执行下个任务，否则会按照重试策略重试，当超出重试策略限制时，会抛出给用户处理，待回调后继续取下一个任务执行；
 3. 重试策略: 可以指定重试次数及重试间隔，若指定次数超出重试间隔数目，则会进入循环，比如：重试次数5，重试时间间隔[1,3,5],则第4次重试会采用1的间隔，第5次重试采用3的间隔...此外还可以指定立刻重试；
 */

@objcMembers class HHSerialTaskManager: NSObject, HHTaskManagerDelegate {
    
    // 任务队列中任务数目
    var taskCount: Int {
        return taskSerialList.count
    }
    
    // 是否已经启动服务
    var isStartService: Bool {
        return self.startServiceFlag
    }
    
    fileprivate var startServiceFlag: Bool = false
    // 是否严格串行
    fileprivate var isStrictSerial: Bool = false
    
    // 创建的任务存储队列
    fileprivate var creatTaskList: [HHTaskModel] = []
    // 可执行的任务队列
    fileprivate var taskSerialList: [HHTaskModel] = []
    // 正在执行的任务队列
    fileprivate var runningTaskList: [HHTaskModel] = []
    
    // 执行任务所在的串行队列
    fileprivate var queue: DispatchQueue?
    // 定时器，用以时间策略重试计时
    fileprivate var timer: Timer?
    fileprivate var timerIsRunning: Bool = false
    // 任务计数，只会递增，用做任务的唯一标识
    fileprivate var count: Int = 0
    
    init(strictSerial: Bool) {
        super.init()
        
        isStrictSerial = strictSerial
    }
    
    deinit {
        debugPrint("HHSerialTaskManager dealloc...")
    }
    
    // MARK: - public
    func clearResource() {
        self.timer?.invalidate()
        self.timer = nil
        self.runningTaskList.removeAll()
        self.taskSerialList.removeAll()
        self.creatTaskList.removeAll()
        self.startServiceFlag = false
    }
    
    func startService() {
        
        if self.startServiceFlag {
            return
        }
        self.startServiceFlag = true
        self.timer = Timer.init(timeInterval: 1.0, target: self, selector: #selector(timerCallBack), userInfo: nil, repeats: true)
        self.timer?.fireDate = Date.distantFuture
        self.queue = DispatchQueue.init(label: "", qos: DispatchQoS.default, attributes: [], autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency.workItem, target: nil)
        
        self.getNextTaskToRun()
    }
    
    func creatTaskNotPromiseSucces(_ task: @escaping TaskBlock) -> String {
        return creatTask(task, overRetry: nil, retryMaxCount: 0, retryTimeStrategy: [], mustSuccess: false, retryAtOnce: false)
    }
    
    func creatTaskFailedRetryAtOnce(_ task: @escaping TaskBlock, overRetry: TaskEndBlock?, retryMaxCount: Int = 3) -> String {
        return creatTask(task, overRetry: overRetry, retryMaxCount: retryMaxCount, retryTimeStrategy: [], mustSuccess: true, retryAtOnce: true)
    }
    
    func creatTask(_ task: @escaping TaskBlock, overRetry: TaskEndBlock?, retryMaxCount: Int = 3, retryTimeStrategy: [Int] = [1, 3, 5]) -> String {
        return creatTask(task, overRetry: overRetry, retryMaxCount: retryMaxCount, retryTimeStrategy: retryTimeStrategy, mustSuccess: true, retryAtOnce: false)
    }
    
    private func creatTask(_ task: @escaping TaskBlock, overRetry: TaskEndBlock?, retryMaxCount: Int = 3, retryTimeStrategy: [Int] = [1, 3, 5], mustSuccess: Bool = false, retryAtOnce: Bool = false) -> String {
        
        self.count += 1
        let model = HHTaskModel(task: task, overRetry: overRetry)
        model.identifier = "\(self.count)"
        model.retryM.retryMaxCount = retryMaxCount
        model.retryM.retryTimeStrategy = retryTimeStrategy
        model.retryM.retryAtOnce = retryAtOnce
        model.mustSuccess = mustSuccess
        synchronized(self) {
            self.creatTaskList.append(model)
        }
        return model.identifier
    }
    
    func addTaskToRun(taskIdentifier: String) {
        
        if let model = self.findHadCreatedTaskModel(taskIdentifier) {
            model.state.isReady = true
            synchronized(self) {
                self.taskSerialList.append(model)
                self.creatTaskList.removeAll { $0 === model }
            }
            getNextTaskToRun()
        }
    }
    
    
    func cancelTask(taskIdentifier: String) {
        if let model = self.findWaitRunTaskModel(taskIdentifier) {
            model.state.isCancel = true
            synchronized(self) {
                self.taskSerialList.removeAll { $0 === model }
                self.creatTaskList.removeAll { $0 === model }
            }
        }
    }
    
    func cancelAllTask() {
        self.creatTaskList.forEach { $0.state.isCancel = true }
        synchronized(self) {
            self.taskSerialList.removeAll()
            self.creatTaskList.removeAll()
        }
    }
    // MARK: - private
    
    /// 从可执行队列中取出下一个可执行任务;
    /// 任务需是就绪状态; 然后取最近一次执行任务且立即重试的，再取就绪状态的任务
    fileprivate func getNextTaskToRun() {
        guard self.runningTaskList.count <= 0, self.startServiceFlag else {
            return
        }
        
        synchronized(self) {
            self.taskSerialList.removeAll { $0.state.isCancel }
        }
        let modelList = self.taskSerialList.filter({ $0.state.isReady })
        
        let waitRunModel: HHTaskModel? = modelList.first
        if let model = waitRunModel, !self.runningTaskList.contains(model) {
            synchronized(self) {
                self.runningTaskList.append(model)
            }
            taskRun(task: model)
        }
        whetherTimerStart()
    }
    
    
    /// 一次任务执行完的善后处理
    /// 非必要成功的则算任务结束；必要成功的则任务状态为成功则任务结束； 否则进入重试策略：处理 是否超出重试次数、是否是立即重试的 或 等待重试时间间隔策略；
    fileprivate func handleTaskRunFinsh(task: HHTaskModel) {
        
        task.retryM.retryIndex += 1
        task.state.isOnceFinsh = true
        task.state.isRuning = false
        task.retryM.timerCount = 0
        task.state.isReady = false
        
        synchronized(self) {
            self.runningTaskList.removeAll { $0 === task }
        }
        
        if task.mustSuccess {
            if task.state.isSuccess {
                synchronized(self) {
                    self.taskSerialList.removeAll { $0 === task }
                }
                getNextTaskToRun()
            } else {
                task.state.isOnceFinsh = false
                if task.retryM.retryIndex > task.retryM.retryMaxCount {
                    synchronized(self) {
                        self.taskSerialList.removeAll { $0 === task }
                    }
                    task.overRetryBlock?({[weak self] in
                        guard let `self` = self else { return }
                        task.state.isSuccess = true
                        self.handleTaskRunFinsh(task: task)
                    })
                } else {
                    if self.isStrictSerial {
                        //...
                    } else {
                        getNextTaskToRun()
                    }
                }
            }
        } else {
            synchronized(self) {
                self.taskSerialList.removeAll { $0 === task }
            }
            getNextTaskToRun()
        }
    }

    fileprivate func taskRun(task: HHTaskModel) {
        task.state.isRuning = true
        let callBack: (Bool)->Void = {[weak self] isSuc in
            guard let `self` = self else { return }
            if task.mustSuccess, !isSuc, task.retryM.retryAtOnce, task.retryM.retryIndex < task.retryM.retryMaxCount {
                task.retryM.retryIndex += 1
                self.taskRun(task: task)
            } else {
                task.state.isSuccess = isSuc
                self.handleTaskRunFinsh(task: task)
            }
        }
        self.queue?.async(execute: {
            task.taskBlock(callBack)
        })
    }
    
    /// 检测重试的失败任务是否达到重试策略的时间间隔, 达到则置为就绪状态
    @objc fileprivate func timerCallBack() {

        let modelList = self.taskSerialList.filter({ $0.state.isReady == false })
        for model in modelList {
            model.retryM.timerCount += 1
            let index = ((model.retryM.retryIndex > 1 ? model.retryM.retryIndex : 1) - 1) % model.retryM.retryTimeStrategy.count
            let time = model.retryM.retryTimeStrategy[index]
            if model.retryM.timerCount >= time {
                model.state.isReady = true
                model.retryM.timerCount = 0
                getNextTaskToRun()
            }
        }
    }
    
    /// 定时器优化：存在可执行任务时启动，无可执行任务是定时器停止
    fileprivate func whetherTimerStart() {
        guard let timer = self.timer else {
            return
        }
        if self.taskSerialList.count > 0 {
            if timerIsRunning {
                return
            }
            timerIsRunning = true
            if Thread.current == Thread.main {
                RunLoop.main.add(timer, forMode: .common)
            } else {
                DispatchQueue.main.sync {
                    RunLoop.main.add(timer, forMode: .common)
                }
            }
            timer.fireDate = Date.distantPast
        } else {
            if timer.isValid {
                timer.invalidate()
                timerIsRunning = false
            }
        }
    }
    
    fileprivate func findHadCreatedTaskModel(_ identifier: String) -> HHTaskModel? {
        return self.creatTaskList.filter { $0.identifier == identifier }.first
    }
    
    fileprivate func findWaitRunTaskModel(_ identifier: String) -> HHTaskModel? {
        return self.taskSerialList.filter { $0.identifier == identifier }.first
    }
}
